
#include <cstdio>
#include <BRender>
#include <BMaterial>
#include <BScene>
#include <BOpenGL>
#include "member_BCamera.h"
#include "member_BGroup.h"
#include "member_BNode.h"
#include "member_BShape.h"
#include "member_BHandler.h"
#include "member_BScene.h"
#include "member_BRender.h"
#include "member_BLight.h"
#include "member_BProgram.h"

using namespace BWE;

extern BArray<GLuint>	UnusedTextureObjects;
extern BArray<GLuint>	UnusedFrameBufferObjects;
extern BArray<GLuint>	UnusedRenderBufferObjects;

member_BCamera::member_BCamera(BCamera* camera)
{
	boss = camera;
	ortho = false;
	center.set(0, 1, 0);
	direct.set(0, 1, 0);
	up.set(0, 0, 1);
	fovy = 36;
	near = 1;
	far = 100;
	lensWidth = 1;
	lensHeight = 1;
	culling = true;
	samples = 9;
	shadowSize = 1024;

	refresh = false;
	scene = 0;
	pixelSizePolicy = Policy_Dynamic;
	dirtyFrustum = true;
	resized = true;
	fbo = 0;
	rbo = 0;
	tbo = 0;
	msfbo = 0;
	msrbo = 0;
	mstbo = 0;
}
member_BCamera::~member_BCamera()
{

}

void member_BCamera::adjustLensSize()
{
	BReal aspect = (BReal)pixelSize.width() / pixelSize.height();
	if (!ortho)
	{
		BReal hfovy = (BReal)(fovy * 0.5 / 180.0 * PI);
		lensHeight = near * tan(hfovy) * (BReal)2.0;
	}
	lensWidth = lensHeight * aspect;
}
void member_BCamera::freshFrustum()
{
	if (dirtyFrustum)
	{
		const BVector& pos = boss->position();
		direct = center - pos;
		direct.normalize();

		right = direct.cross(up);
		right.normalize();

		BVector relup = right.cross(direct).normal();
		BVector dir_up = relup * lensHeight * BReal(0.5);
		BVector dir_right = right * lensWidth * BReal(0.5);

		BVector near_center = pos + direct * near;
		BVector near_v0 = near_center - dir_right + dir_up;
		BVector near_v1 = near_center - dir_right - dir_up;
		BVector near_v2 = near_center + dir_right - dir_up;
		BVector near_v3 = near_center + dir_right + dir_up;

		frustum.setNear(0, near_v0);
		frustum.setNear(1, near_v1);
		frustum.setNear(2, near_v2);
		frustum.setNear(3, near_v3);

		if (ortho)
		{
			BReal depth = far - near;
			BVector far_v0 = near_v0 + direct * depth;
			BVector far_v1 = near_v1 + direct * depth;
			BVector far_v2 = near_v2 + direct * depth;
			BVector far_v3 = near_v3 + direct * depth;

			frustum.setFar(0, far_v0);
			frustum.setFar(1, far_v1);
			frustum.setFar(2, far_v2);
			frustum.setFar(3, far_v3);

			frustum.setPlane(0, BPlane(direct, pos + direct * near));
			frustum.setPlane(1, BPlane(dir_right, near_v0));//left
			frustum.setPlane(2, BPlane(-dir_right, near_v3));//right
			frustum.setPlane(3, BPlane(-dir_up, near_v0));//top
			frustum.setPlane(4, BPlane(dir_up, near_v1));//bottom
			frustum.setPlane(5, BPlane(-direct, pos + direct * far));
		}
		else
		{
			BReal ratio = far / near;
			BVector far_v0 = pos + (near_v0 - pos) * ratio;
			BVector far_v1 = pos + (near_v1 - pos) * ratio;
			BVector far_v2 = pos + (near_v2 - pos) * ratio;
			BVector far_v3 = pos + (near_v3 - pos) * ratio;

			frustum.setFar(0, far_v0);
			frustum.setFar(1, far_v1);
			frustum.setFar(2, far_v2);
			frustum.setFar(3, far_v3);

			frustum.setPlane(0, BPlane(direct, pos + direct * near));//near
			frustum.setPlane(1, BPlane(near_v0, pos, near_v1));//left
			frustum.setPlane(2, BPlane(near_v2, pos, near_v3));//right
			frustum.setPlane(3, BPlane(near_v3, pos, near_v0));//top
			frustum.setPlane(4, BPlane(near_v1, pos, near_v2));//bottom
			frustum.setPlane(5, BPlane(-direct, pos + direct * far));//far
		}

		dirtyFrustum = false;
	}
}

void member_BCamera::prepare()
{
	shapeArray.reset();
	transShapeQueue.reset();
	if (scene_member(scene)->sceneOctTree)
	{
		sceneItems.reset();
		scene_member(scene)->sceneOctTree->intersect(frustum.space(), sceneItems);
		for (int i = 0; i < sceneItems.size(); i++)
		{
			SceneOctTreeItem& item = sceneItems[i];
			prepare(item);
		}
	}
	else
	{
		sceneItems.clear();
		BSet<BNodeHolder>& nodes = scene_member(scene)->nodes;
		BSet<BShapeHolder>& shapes = scene_member(scene)->shapes;
		prepareShapes(nodes, shapes);
		BSet<BGroupHolder>& groups = scene_member(scene)->groups;
		for (int i = 0; i < groups.size(); i++)
		{
			prepare(groups[i]);
		}
	}
}
void member_BCamera::prepare(SceneOctTreeItem& item)
{
	BArray<BNodeHolder>& nodes = item->nodes;
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		if (frustum.clip(node->space()))
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix();
			if (!shape_member(shape)->isolate)
				matrix *= nodeMatrix;
			BBox box = shape->box() * matrix;
			if (frustum.clip(box.space()))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = shape_member(shape)->isolate ? 0 : node;
			sinfo.space = box.space();
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane(0).distance(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	BArray<BShapeHolder>& shapes = item->shapes;
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		const BSpace& space = shape->space();
		if (frustum.clip(space))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.space = space;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane(0).distance(space.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}
void member_BCamera::prepare(BGroup* group)
{
	const BString& name = group->name();
	if (group_member(group)->visible)
	{
		BSet<BNodeHolder>& nodes = group_member(group)->nodes;
		BSet<BShapeHolder>& shapes = group_member(group)->shapes;
		prepareShapes(nodes, shapes);
		BSet<BGroupHolder>& children = group_member(group)->children;
		for (int i = 0; i < children.size(); i++)
		{
			prepare(children[i]);
		}
	}
}
void member_BCamera::prepareShapes(BSet<BNodeHolder>& nodes, BSet<BShapeHolder>& shapes)
{
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		if (frustum.clip(node->space()))
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix();
			if (!shape_member(shape)->isolate)
				matrix *= nodeMatrix;

			BBox box = shape->box() * matrix;
			if (frustum.clip(box.space()))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = shape_member(shape)->isolate ? 0 : node;
			sinfo.space = box.space();
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane(0).distance(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		const BSpace& space = shape->space();
		if (frustum.clip(space))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.space = space;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane(0).distance(space.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}

void member_BCamera::frame(BScene* scene, BHandler* handler)
{
	this->scene = scene;
	
	const BLightSet& lights = scene_member(scene)->lights;
	for (int i = 0; i < lights.size(); i++)
	{
		BLight* light = lights[i];
		//light_member(light)->frameShadow(scene);
	}

	while (refresh)
	{
		refresh = false;

		int width = pixelSize.width();
		int height = pixelSize.height();
		if (fbo == 0 || rbo == 0 || tbo == 0)
			resized = true;
		if (resized)
		{
			GLint backup_frame_buffer;
			glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);
			if (samples > 0)
			{
				if (!msfbo)
				{
					glGenFramebuffers(1, &msfbo);
					glGenRenderbuffers(1, &msrbo);
					glGenTextures(1, &mstbo);
				}
				if (msfbo)
				{
					glBindFramebuffer(GL_FRAMEBUFFER, msfbo);

					glBindRenderbuffer(GL_RENDERBUFFER, msrbo);
					glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, width, height);
					glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msrbo);

					glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mstbo);
					glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA, width, height, GL_TRUE);
					glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D_MULTISAMPLE, mstbo, 0);

					GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
					if (status != GL_FRAMEBUFFER_COMPLETE)
						return;
				}
			}
			else
			{
				if (msfbo)
				{
					UnusedFrameBufferObjects.append(msfbo);
					msfbo = 0;
				}
				if (msrbo)
				{
					UnusedRenderBufferObjects.append(msrbo);
					msrbo = 0;
				}
				if (mstbo)
				{
					UnusedTextureObjects.append(mstbo);
					mstbo = 0;
				}
			}
			if (!fbo)
			{
				glGenFramebuffers(1, &fbo);
				glGenRenderbuffers(1, &rbo);
			}
			if (fbo)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glBindRenderbuffer(GL_RENDERBUFFER, rbo);
				glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

			}
			if (!tbo)
			{
				glGenTextures(1, &tbo);
			}
			if (tbo)
			{
				glBindTexture(GL_TEXTURE_2D, tbo);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tbo, 0);
			}
			glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
			resized = false;
		}
		if (dirtyFrustum)
		{
			freshFrustum();
		}
		prepare();

		BRender render(boss);

		glClearColor(color.r() / 255.0f, color.g() / 255.0f, color.b() / 255.0f, color.a() / 255.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glClearDepth(1);
		glClear(GL_DEPTH_BUFFER_BIT);

		render_member(&render)->lights = &scene_member(scene)->lights;

		//GLfloat fogColor[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
		//glEnable(GL_FOG);
		//glFogi(GL_FOG_MODE, GL_LINEAR);
		//glFogfv(GL_FOG_COLOR, fogColor);
		//glFogf(GL_FOG_DENSITY, 0.6f);
		//glHint(GL_FOG_HINT, GL_NICEST);
		//glFogf(GL_FOG_START, near);
		//glFogf(GL_FOG_END, far);

		for (int i = 0; i < shapeArray.size(); i++)
		{
			ShapeInfo& sinfo = shapeArray[i];
			drawShape(sinfo, &render);
		}
		for (auto it = transShapeQueue.rbegin(); it != transShapeQueue.rend(); --it)
		{
			ShapeInfo& sinfo = *it;
			drawShape(sinfo, &render);
		}
		renderHandler(handler, render);
	}
	this->scene = 0;
}
void member_BCamera::drawShape(ShapeInfo& sinfo, BRender* render)
{
	BNode* node = sinfo.node;
	BShape* shape = sinfo.shape;

	render_member(render)->node = sinfo.node;
	render_member(render)->shape = sinfo.shape;

	glPushMatrix();
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	render->setNormalize(shape_member(shape)->normalize);
	render->setLighting(shape_member(shape)->lighting);
	render->setDepthTest(shape_member(shape)->depthTest);
	render->setDepthOffset(shape_member(shape)->depthOffset);
	render->setCullback(shape_member(shape)->cullback);
	render->setPointSmooth(shape_member(shape)->pointSmooth);
	render->setPointSize(shape_member(shape)->pointSize);
	render->setLineSmooth(shape_member(shape)->lineSmooth);
	render->setLineWidth(shape_member(shape)->lineWidth);
	render->setPolySmooth(shape_member(shape)->polySmooth);
	render->setPolyMode(shape_member(shape)->polyMode);

	if (render_member(render)->lighting && render_member(render)->lights)
	{
		render_member(render)->useLights(sinfo.space.center());
		const BMaterial* material = shape_member(shape)->material;
		if (material && material->enabled())
		{
			render->setMaterial(material);
			glDisable(GL_COLOR_MATERIAL);
		}
		else
		{
			glEnable(GL_COLOR_MATERIAL);
			render->setColor(shape_member(shape)->color);
		}
	}
	else
	{
		glDisable(GL_COLOR_MATERIAL);
		render->setColor(shape_member(shape)->color);
	}

	if (node)
		BOpenGL::glMultMatrix(node_member(node)->matrix);

	BOpenGL::glMultMatrix(shape_member(shape)->matrix);

	const BProgram* program = shape_member(shape)->program;
	if (program)
		render->setProgram(program);

	if (shape_member(shape)->textures.size())
	{
		BArray<BTextureHolder>& textures = shape_member(shape)->textures;
		for (int i = 0; i < textures.size(); i++)
		{
			BTexture* texture = textures[i];
			render->setTexture(texture, i);
		}
	}

	shape_member(shape)->render(*render);

	if (program)
		render->setProgram(0);

	if (int texcount = shape_member(shape)->textures.size())
	{
		for (int i = 0; i < texcount; i++)
		{
			render->setTexture(0, i);
		}
	}

	render_member(render)->node = 0;
	render_member(render)->shape = 0;

	glPopAttrib();
	glPopMatrix();
}
void member_BCamera::renderHandler(BHandler* handler, BRender& render)
{
	if (handler && handler->enabled())
	{
		glPushMatrix();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		handler_member(handler)->render(render);
		glPopAttrib();
		glPopMatrix();
		for (int i = 0; i < handler_member(handler)->children.size(); i++)
		{
			BHandler* child = handler_member(handler)->children[i];
			renderHandler(child, render);
		}
	}
}

